﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.ServiceModel;
using System.Linq;
using XchangeStreamer.PublisherSdk.MarketDataFeedService;

namespace XchangeStreamer.PublisherSdk
{
    public sealed class Connector : IDisposable
    {
        #region Private Fields

        #region Basic

        private static Connector connection;
        private PublisherClient client;
        private Timer timer;
        private long disposed = 0;
        private long connected = 0;
        private long error = 0;
        private NetTcpBinding netTcp;
        private EndpointAddress address;
        private object locker = new object();

        #endregion

        #endregion

        #region Private Const. / Dest.

        private Connector()
        {
        }

        ~Connector()
        {
            this.Disconnect(false);
        }

        #endregion

        #region Public Properties

        public static Connector Connection
        {
            get
            {
                if (connection == null)
                {
                    lock (typeof(Connector))
                    {
                        if (connection == null)
                        {
                            connection = new Connector();
                        }
                    }
                }
                return connection;
            }
        }

        #endregion

        #region Public Methods

        public void Connect()
        {
            if (Interlocked.CompareExchange(ref this.connected, 1, 0) == 0)
            {
                Task.Factory.StartNew(() =>
                {
                    lock (this.locker)
                    {
                        this.Init();
                        //this.client = new PublisherClient("NetTcpBinding_IPublisher");
                        this.client = new PublisherClient(this.netTcp, this.address);
                        this.Start();

                        Interlocked.Exchange(ref this.disposed, 0);
                    }
                });
            }
        }

        public void Disconnect()
        {
            this.Disconnect(true);
        }

        #endregion

        #region Private Methods

        private void Init()
        {
            // NetTcp Binding
            this.netTcp = new NetTcpBinding(SecurityMode.None, true);

            this.netTcp.OpenTimeout = TimeSpan.FromDays(1);
            this.netTcp.CloseTimeout = TimeSpan.FromDays(1);
            this.netTcp.SendTimeout = TimeSpan.FromDays(1);
            this.netTcp.ReceiveTimeout = TimeSpan.FromDays(5);

            this.netTcp.Security.Mode = SecurityMode.None;
            this.netTcp.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
            this.netTcp.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;

            this.netTcp.ListenBacklog = int.MaxValue;
            this.netTcp.MaxBufferPoolSize = int.MaxValue;
            this.netTcp.MaxBufferSize = int.MaxValue;

            /*
             * (Not Supported on Mono)
             * 
            this.netTcp.MaxConnections = 1000;
            */

            this.netTcp.MaxReceivedMessageSize = int.MaxValue;
            this.netTcp.PortSharingEnabled = false;
            this.netTcp.TransferMode = TransferMode.Buffered;

            this.netTcp.ReaderQuotas.MaxArrayLength = int.MaxValue;
            this.netTcp.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
            this.netTcp.ReaderQuotas.MaxDepth = int.MaxValue;
            this.netTcp.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
            this.netTcp.ReaderQuotas.MaxStringContentLength = int.MaxValue;

            /*
             * (Not Supported on Mono)
             * 
            this.netTcp.ReliableSession.Enabled = true;
            this.netTcp.ReliableSession.InactivityTimeout = TimeSpan.FromDays(5);
            this.netTcp.ReliableSession.Ordered = true;
            */

            this.netTcp.ReliableSession.Enabled = false;
            this.netTcp.ReliableSession.InactivityTimeout = TimeSpan.FromDays(5);
            this.netTcp.ReliableSession.Ordered = true;

            this.address = new EndpointAddress("net.tcp://localhost:7002/MarketDataFeedService/Publisher");
        }

        private void Start()
        {
            bool opened = false;
            while (!opened)
            {
                try
                {
                    this.client.InnerChannel.Opening += new EventHandler(InnerChannel_Opening);
                    this.client.InnerChannel.Opened += new EventHandler(InnerChannel_Opened);
                    this.client.InnerChannel.Open();
                    opened = true;
                }
                catch (Exception ex) { Console.WriteLine(ex.Message); }
            }

            this.client.InnerChannel.Faulted += new EventHandler(this.InnerDuplexChannel_Faulted);

            Console.WriteLine("Connected.");

            this.timer = new Timer(new TimerCallback(this.TimerCallback), null, 0, 1000);
        }

        void InnerChannel_Opened(object sender, EventArgs e)
        {
            Console.WriteLine("Opened.");
        }

        void InnerChannel_Opening(object sender, EventArgs e)
        {
            Console.WriteLine("Opening.");
        }

        private void Disconnect(bool suppressFinalize)
        {
            if (Interlocked.CompareExchange(ref this.disposed, 1, 0) == 0)
            {
                lock (this.locker)
                {
                    if (this.timer != null)
                    {
                        this.timer.Dispose();
                        this.timer = null;
                    }

                    try
                    {
                    }
                    catch { }
                    finally
                    {
                        if (this.client != null)
                        {
                            this.client.Close();
                            this.client = null;
                        }

                        Interlocked.Exchange(ref this.connected, 0);

                        if (suppressFinalize)
                        {
                            GC.SuppressFinalize(this);
                        }
                    }
                }
            }
        }

        private int mesgId = 0;
        private void TimerCallback(object obj)
        {
            //this.timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            if (Interlocked.Read(ref this.error) == 0)
            {
                try
                {
                    TimeSpan now = DateTime.UtcNow.TimeOfDay;
                    byte[] message = new byte[1024 * 100];

                    this.client.Publish(new XchangeMessage(Interlocked.Increment(ref this.mesgId), now.ToString(), 1, message));
                }
                catch (Exception ex) { Console.WriteLine(ex.Message); }
            }
        }

        private void InnerDuplexChannel_Faulted(object sender, EventArgs e)
        {
            Console.WriteLine("Faulted");
            if (Interlocked.CompareExchange(ref this.error, 1, 0) == 0)
            {
                this.client.InnerChannel.Faulted -= new EventHandler(this.InnerDuplexChannel_Faulted);
                if (this.disposed == 1 || this.connected == 1)
                {
                    if (this.timer != null)
                    {
                        this.timer.Dispose();
                        this.timer = null;
                    }

                    this.Start();
                    Interlocked.Exchange(ref this.error, 0);
                }
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.Disconnect(true);
        }

        #endregion
    }
}